Le porte e i messaggi I messaggi e le porte sono il vero mezzo che permettono l'intercomunicazione fra task e s.o. (i segnali servivano solo per avvertire riguardo ad un particolare evento ma non permettono di scambiare dati); le porte vengono create dai task e permettono di ricevere il messaggio spedito (sono delle "porte" sul mondo di intercomunicazione), che altro non è che un blocco di dati il cui puntatore viene passato al task che riceve il messaggio e tramite il quale può acquisire tutte le informazioni che il mittente vuole inviargli; questo mezzo fa uso dei segnali (per avvertire il task che un messaggio è arrivato, segnale che comunque viene gestito dal s.o. che quindi non necessita di essere allocato o gestito dal programmatore) ed inoltre viene molto utilizzato da Intuition. Pensate a questa similitudine per comprendere bene come funziona tutto il meccanismo: supponete che voi (il task) dopo una nottata passata a lavorare davanti al vostro Amiga volete dormire un po' di più la mattina e quindi staccate il telefono, ponete la sveglia all'orario giusto (i segnali) e vi mettete a dormire (stato di sleeping); la mattina però arriva il postino che bussa due volte (tipico segnale del postino da non confondere con il vicino che anche se suona il campanello, si vuole dormire e si fa finta di niente), quindi vi svegliate (stato attivo) e prelevate il messaggio aprendo la porta. Vi sono comunque delle puntualizzazioni sui messaggi da considerare: il messaggio è una struttura dati composta da alcune informazioni necessarie al sistema e dal messaggio vero e proprio; il messaggio deve essere spedito ad una precisa porta, viene passato per indirizzo e deve essere risposto, cioè quando il programma ne riceve uno deve rispondere al mittente per far capire che il messaggio è stato ricevuto, inoltre quelli arrivati ad una porta vengono mantenuti in una coda in modo che nessuno di questi venga perso. La porta come abbiamo detto è l'apertura sul mondo dei messaggi ed è identificata da questa struttura: struct MsgPort { struct Node mp_Node; UBYTE mp_Flags; UBYTE mp_SigBit; struct Task *mp_SigTask; struct List mp_MsgList; }; mp_Node serve perché tutte le porte vengono mantenute in una lista gestita dal s.o.; mp_Flags indica quale azione comporta l'arrivo di un messaggio a questa porta (vedere il riquadro); mp_SigBit è il numero di segnale utilizzato per segnalare al task che un messaggio è arrivato; mp_SigTask è il puntatore alla struttura task che indica il task a cui la porta è associata e che quindi deve essere segnalato (si possono creare più porte per un task); mp_SigTask può anche essere il puntatore ad una struttura Interrupt per la chiamata ad un interrupt software a seconda del valore di mp_Flags; mp_SigList è la lista dei messaggi arrivati alla porta che devono essere prelevati e risposti. Per creare una porta si utilizza la funzione CreatePort: Porta = (struct MsgPort *)CreatePort(nome,priorità); dove "nome" è il puntatore ad una stringa che identifica univocamente la porta e "priorità" è la priorità che la porta avrà sulle altre (normalmente 0), il valore ritornato Porta è il puntatore alla struttura MsgPort creata da CreatePort; questa funzione assegna automaticamente la porta alla lista pubblica delle porte tramite il nome "nome" che permette di identificarla; vi è dalla V36 del s.o. una funzione equivalente: Porta = CreateMsgPort(); questa funzione però non inserisce la porta nella lista di utilizzo pubblico; per poterla inserire nella lista pubblica (quindi creata con CreateMsgPort) occorre utilizzare AddPort, e per rimuoverla RemPort. La possibilità di inserire la porta nella lista pubblica ne permette l'utilizzo anche ad altri programmi che possono ricercare l'indirizzo della sua struttura (per potergli lanciare un messaggio) mediante questa funzione: Porta = (struct MsgPort *)FindPort(nome); FindPort insieme alla funzione per spedire il messaggio devono essere usate insieme (vale a dire, prima di lanciare un messaggio ricercare sempre la porta) e precedute da una chiamata Forbid(); (funzione di exec che inibisce l'attività degli altri task) e seguite da Permit(); (per ritornare alla situazione normale dopo Forbid), questo perché il task che ha creato la porta, potrebbe rimuoverla in un qualsiasi momento. Per rimuovere la porta creata dopo che non serve più si utilizzano le corrispettive funzioni di quelle utilizzate DeletePort e DeleteMsgPort con queste sintassi: DeletePort(Porta); DeleteMsgPrt(Porta); { V36 } Illustriamo ora come i messaggi vengano inviati tramite le porte; innanzitutto, la struttura dati associata ad un messaggio è la seguente: struct Message { struct Node mn_Node; struct MsgPort *mn_ReplyPort; UWORD mn_Length; }; mn_Node serve perché i messaggi vengono mantenuti in una lista del s.o.; mn_ReplyPort è il puntatore alla porta che ha inviato il messaggio, a cui il programma deve rispondere dopo aver ricevuto quest'ultimo; mn_Length indica la lunghezza del messaggio compresa la struttura appena illustrata. Questa struttura indica solo le informazioni base per l'utilizzo del messaggio, ma il messaggio vero e proprio dove è, e che struttura ha? Il messaggio è situato nei bytes immediatamente successivi a quelli della struttura e possono avere qualsiasi forma vogliate e qualsiasi lunghezza (nei limiti imposti da mn_Length); per cui la forma di un ipotetico messaggio può essere questa: struct Mio_Messaggio { struct Message Mio_Msg; ULONG Mio_Dato1,_MioDato2; char Mio_Nome[20]; /* ecc... */ }; la struttura Message deve quindi essere sempre presente in testa ad ogni messaggio. Per inviare un messaggio ad una porta occorre utilizzare la funzione di exec PutMsg: PutMsg(porta,messaggio); dove "porta" è il puntatore alla struttura MsgPort che identifica la porta a cui inviare il messaggio e "messaggio" è il puntatore alla struttura Message del messaggio da inviare; si consiglia di utilizzare questa funzione insieme a FindPort e di racchiuderle fra due chiamate Forbid() e Permit(), poiché il task della porta che riceve il messaggio, può decidere di chiuderla in un qualsiasi momento! Tramite la chiamata Forbid(), si impedisce il multitasking per cui il task non può fare nulla e tantomeno chiudere una porta; Permit() in ultimo riporta il sistema alla normalità. Qualche riga sopra è stato scritto si consiglia perché non è detto che sia sempre necessario comportarsi così; mettiamo infatti che il task proprietario della porta sia creato e messo in funzione dal vostro programma (che è un task a sua volta); in questo caso sapete benissimo a priori come questo si comporterà, e magari sarà il vostro programma a decidere quando fargli chiudere la porta, per cui il problema non si pone. Per attendere un messaggio, dovreste averlo capito dalla scorsa puntata, occorre utilizzare la funzione Wait; il codice del segnale per cui Wait deve attendere è presente nella struttura MsgPort della porta ché riceverà il segnale; per cui il codice con l'istruzione Wait per attendere il messaggio di una porta "porta" può avere la seguente forma: struct MsgPort *porta; ULONG SegnPorta,segnali; . . SegnPorta = 1 << porta->mp_SigBit; segnali = Wait(SegnPorta); Se però come nel caso appena descritto, l'evento per cui attendere è solo un messaggio, si può utilizzare una funzione che fa tuttò ciò automaticamente, passando semplicemente il puntatore alla porta per cui ricevere il messaggio: messaggio = (struct Message *)WaitPort(porta); "messaggio" è il puntatore alla struttura Message del messaggio arrivato alla porta; occorre ricordare che il sistema mantiene una lista FIFO (First In First Out, cioè il primo ad entrare è il primo ad uscire) di tutti i messaggi che arrivano alla porta per evitare che vadano persi; Wait e WaitPort non eliminano il messaggio dalla lista, per cui occorre utilizzare la funzione GetMsg() che ritorna anch'essa il puntatore del primo messaggio rimasto nella lista, eliminandolo però da quest'ultima (morale: "messaggio" ritornato da WaitPort non serve a nulla, poiché deve essere ripescato da GetMsg): messaggio = (struct Message *)GetMsg(porta); Come già accennato, bisogna rispondere ad ogni messaggio arrivato alla propria porta per segnalare che il messaggio è stato ricevuto correttamente, mediante la funzione ReplyMsg: ReplyMsg(messaggio); dove "messaggio" è il puntatore al messaggio a cui bisogna rispondere.